嗨嗨!大家好!歡迎來到 Rust 三十天挑戰的第十一天!
昨天我們學習了泛型,掌握了如何寫出彈性且抽象的程式碼。今天我們要來探討一個與泛型緊密相關,且同樣重要的概念:特徵 (Traits)。
如果說泛型讓我們能夠「寫出適用於多種型別的程式碼」,那麼 Traits 就是「定義這些型別應該具備什麼行為」的機制。你可以把 Traits 想像成其他語言中的介面 (Interface) 或抽象類別,但 Rust 的 Traits 比它們更加強大和靈活。
老實說,當我剛開始接觸 Traits 時,覺得它們就是介面的 Rust 版本。但隨著深入了解,我發現 Traits 的設計哲學更加優雅:它們不只是定義「某個型別能做什麼」,更是實現了「能力導向的程式設計」—你不需要關心物件的具體型別,只需要關心它具備什麼能力。
今天讓我們一起探索這個讓 Rust 程式碼既抽象又高效的強大特性!
Trait 是一種定義共享行為的方式。它告訴 Rust 編譯器某個特定型別具有哪些功能,並且這些功能可以與其他型別共享。
// 定義一個 Trait
trait Drawable {
fn draw(&self);
}
// 為不同型別實現這個 Trait
struct Circle {
radius: f64,
}
struct Rectangle {
width: f64,
height: f64,
}
impl Drawable for Circle {
fn draw(&self) {
println!("畫一個半徑為 {:.2} 的圓形", self.radius);
}
}
impl Drawable for Rectangle {
fn draw(&self) {
println!("畫一個 {:.2} x {:.2} 的長方形", self.width, self.height);
}
}
fn main() {
let circle = Circle { radius: 5.0 };
let rectangle = Rectangle { width: 10.0, height: 8.0 };
circle.draw(); // 畫一個半徑為 5.00 的圓形
rectangle.draw(); // 畫一個 10.00 x 8.00 的長方形
}
trait TraitName {
// 方法簽章(無預設實現)
fn required_method(&self) -> ReturnType;
// 有預設實現的方法
fn default_method(&self) {
println!("這是預設實現");
}
// 關聯函式(類似靜態方法)
fn associated_function() -> String {
String::from("關聯函式")
}
// 關聯型別
type AssociatedType;
// 關聯常數
const ASSOCIATED_CONST: u32 = 42;
}
struct MyStruct {
value: i32,
}
impl TraitName for MyStruct {
type AssociatedType = String;
fn required_method(&self) -> String {
format!("值是 {}", self.value)
}
// 可以選擇覆寫預設實現
fn default_method(&self) {
println!("自訂的實現,值是 {}", self.value);
}
}
use std::fmt;
struct Point {
x: f64,
y: f64,
}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({:.2}, {:.2})", self.x, self.y)
}
}
struct Person {
name: String,
age: u32,
}
impl fmt::Display for Person {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({}歲)", self.name, self.age)
}
}
fn main() {
let point = Point { x: 3.14, y: 2.71 };
let person = Person { name: "Alice".to_string(), age: 25 };
println!("座標點:{}", point);
println!("人員:{}", person);
// Display trait 讓我們可以使用 {} 格式化
let formatted = format!("座標:{},人員:{}", point, person);
println!("{}", formatted);
}
#[derive(Debug)]
struct Student {
name: String,
grade: u32,
age: u32,
}
impl PartialEq for Student {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.age == other.age
}
}
impl Eq for Student {}
impl PartialOrd for Student {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Student {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// 先按成績排序,再按年齡排序
match self.grade.cmp(&other.grade) {
std::cmp::Ordering::Equal => self.age.cmp(&other.age),
other => other,
}
}
}
fn main() {
let mut students = vec![
Student { name: "Alice".to_string(), grade: 85, age: 20 },
Student { name: "Bob".to_string(), grade: 92, age: 19 },
Student { name: "Charlie".to_string(), grade: 85, age: 21 },
];
// 現在可以比較學生
println!("Alice == Bob: {}", students[0] == students[1]);
println!("Alice < Bob: {}", students[0] < students[1]);
// 現在可以排序
students.sort();
println!("排序後的學生:");
for student in &students {
println!(" {:?}", student);
}
}
#[derive(Debug)]
struct Book {
title: String,
author: String,
pages: u32,
}
impl Clone for Book {
fn clone(&self) -> Self {
println!("正在複製書籍:{}", self.title);
Book {
title: self.title.clone(),
author: self.author.clone(),
pages: self.pages,
}
}
}
fn main() {
let original = Book {
title: "Rust 程式設計".to_string(),
author: "Rust 社群".to_string(),
pages: 500,
};
let copy = original.clone();
println!("原書:{:?}", original);
println!("副本:{:?}", copy);
// 證明它們是不同的實例
println!("是同一本書嗎?{}", std::ptr::eq(&original, ©));
}
trait Summary {
fn summarize(&self) -> String;
}
struct NewsArticle {
headline: String,
content: String,
author: String,
}
struct Tweet {
username: String,
content: String,
reply: bool,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{} - by {}", self.headline, self.author)
}
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.content)
}
}
// 接受任何實現了 Summary 的型別
fn notify(item: &impl Summary) {
println!("突發新聞!{}", item.summarize());
}
// 等價的 Trait bound 語法
fn notify_v2<T: Summary>(item: &T) {
println!("突發新聞!{}", item.summarize());
}
// 多個 Trait bounds
fn notify_and_display<T: Summary + std::fmt::Display>(item: &T) {
println!("顯示:{}", item);
println!("摘要:{}", item.summarize());
}
fn main() {
let article = NewsArticle {
headline: "Rust 1.75 發布!".to_string(),
content: "新版本帶來了許多改進...".to_string(),
author: "Rust 團隊".to_string(),
};
let tweet = Tweet {
username: "rustlang".to_string(),
content: "新版本發布了!快來試試看新功能 #rust".to_string(),
reply: false,
};
notify(&article);
notify(&tweet);
notify_v2(&article);
}
fn create_summary(use_tweet: bool) -> impl Summary {
if use_tweet {
Tweet {
username: "example".to_string(),
content: "這是一個範例推文".to_string(),
reply: false,
}
} else {
// 注意:這會編譯錯誤!impl Trait 只能回傳單一具體型別
// NewsArticle { ... }
Tweet {
username: "news".to_string(),
content: "新聞內容".to_string(),
reply: false,
}
}
}
use std::fmt::Display;
struct Pair<T> {
first: T,
second: T,
}
impl<T> Pair<T> {
fn new(first: T, second: T) -> Self {
Self { first, second }
}
}
// 只為實現了 Display + PartialOrd 的型別實現這個方法
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.first >= self.second {
println!("最大的是 first = {}", self.first);
} else {
println!("最大的是 second = {}", self.second);
}
}
}
fn main() {
let pair = Pair::new(10, 20);
pair.cmp_display(); // 這個方法只有在 T 實現了 Display + PartialOrd 時才可用
let string_pair = Pair::new("hello", "world");
string_pair.cmp_display();
}
struct Counter {
current: usize,
max: usize,
}
impl Counter {
fn new(max: usize) -> Counter {
Counter { current: 0, max }
}
}
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.max {
let current = self.current;
self.current += 1;
Some(current)
} else {
None
}
}
}
fn main() {
let counter = Counter::new(5);
// 現在可以使用所有迭代器方法
let doubled: Vec<usize> = counter
.map(|x| x * 2)
.filter(|&x| x > 2)
.collect();
println!("雙倍過濾後:{:?}", doubled);
// 使用 for 迴圈
for num in Counter::new(3) {
println!("計數:{}", num);
}
}
#[derive(Debug)]
struct Person {
name: String,
age: u32,
}
// 實現 From trait
impl From<(&str, u32)> for Person {
fn from(data: (&str, u32)) -> Self {
Person {
name: data.0.to_string(),
age: data.1,
}
}
}
impl From<String> for Person {
fn from(name: String) -> Self {
Person { name, age: 0 }
}
}
// Into trait 會自動實現
fn main() {
// 使用 From
let person1 = Person::from(("Alice", 25));
let person2 = Person::from("Bob".to_string());
println!("Person 1: {:?}", person1);
println!("Person 2: {:?}", person2);
// 使用 Into
let person3: Person = ("Charlie", 30).into();
let person4: Person = "Diana".to_string().into();
println!("Person 3: {:?}", person3);
println!("Person 4: {:?}", person4);
// 在函式參數中使用
create_person(("Eve", 28));
create_person("Frank".to_string());
}
fn create_person<T: Into<Person>>(data: T) {
let person = data.into();
println!("建立了人員:{:?}", person);
}
#[derive(Debug)]
struct Config {
host: String,
port: u16,
debug: bool,
max_connections: u32,
}
impl Default for Config {
fn default() -> Self {
Config {
host: "localhost".to_string(),
port: 8080,
debug: false,
max_connections: 100,
}
}
}
impl Config {
fn new() -> Self {
Self::default()
}
fn with_host(mut self, host: String) -> Self {
self.host = host;
self
}
fn with_port(mut self, port: u16) -> Self {
self.port = port;
self
}
fn with_debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
}
fn main() {
// 使用預設值
let default_config = Config::default();
println!("預設設定:{:?}", default_config);
// 建構者模式
let custom_config = Config::new()
.with_host("example.com".to_string())
.with_port(3000)
.with_debug(true);
println!("自訂設定:{:?}", custom_config);
}
關聯型別讓 Trait 更加靈活,當一個 Trait 對於一個型別只有一種合理的實現時使用。
trait Container {
type Item;
fn get(&self, index: usize) -> Option<&Self::Item>;
fn len(&self) -> usize;
fn push(&mut self, item: Self::Item);
}
struct IntContainer {
items: Vec<i32>,
}
impl Container for IntContainer {
type Item = i32;
fn get(&self, index: usize) -> Option<&Self::Item> {
self.items.get(index)
}
fn len(&self) -> usize {
self.items.len()
}
fn push(&mut self, item: Self::Item) {
self.items.push(item);
}
}
struct StringContainer {
items: Vec<String>,
}
impl Container for StringContainer {
type Item = String;
fn get(&self, index: usize) -> Option<&Self::Item> {
self.items.get(index)
}
fn len(&self) -> usize {
self.items.len()
}
fn push(&mut self, item: Self::Item) {
self.items.push(item);
}
}
// 泛型函式使用關聯型別
fn print_container<C: Container>(container: &C)
where
C::Item: std::fmt::Display,
{
println!("容器包含 {} 個項目:", container.len());
for i in 0..container.len() {
if let Some(item) = container.get(i) {
println!(" [{}]: {}", i, item);
}
}
}
fn main() {
let mut int_container = IntContainer { items: vec![] };
int_container.push(1);
int_container.push(2);
int_container.push(3);
let mut string_container = StringContainer { items: vec![] };
string_container.push("Hello".to_string());
string_container.push("World".to_string());
print_container(&int_container);
print_container(&string_container);
}
有時候我們需要在執行時決定要呼叫哪個實現,這時可以使用 Trait Objects。
trait Animal {
fn make_sound(&self);
fn name(&self) -> &str;
}
struct Dog {
name: String,
}
struct Cat {
name: String,
}
struct Bird {
name: String,
}
impl Animal for Dog {
fn make_sound(&self) {
println!("汪汪!");
}
fn name(&self) -> &str {
&self.name
}
}
impl Animal for Cat {
fn make_sound(&self) {
println!("喵喵!");
}
fn name(&self) -> &str {
&self.name
}
}
impl Animal for Bird {
fn make_sound(&self) {
println!("啾啾!");
}
fn name(&self) -> &str {
&self.name
}
}
fn main() {
// 使用 Trait Objects 儲存不同型別的動物
let animals: Vec<Box<dyn Animal>> = vec![
Box::new(Dog { name: "小白".to_string() }),
Box::new(Cat { name: "小花".to_string() }),
Box::new(Bird { name: "小鳥".to_string() }),
];
// 動態分配:在執行時決定呼叫哪個實現
for animal in &animals {
println!("{} 說:", animal.name());
animal.make_sound();
}
// 也可以使用參考
let dog = Dog { name: "大黃".to_string() };
let cat = Cat { name: "小黑".to_string() };
let animal_refs: Vec<&dyn Animal> = vec![&dog, &cat];
println!("\n使用參考:");
for animal in animal_refs {
println!("{} 說:", animal.name());
animal.make_sound();
}
}
trait Animal {
fn name(&self) -> &str;
}
trait Dog: Animal { // Dog 是 Animal 的子 trait
fn bark(&self);
}
struct Labrador {
name: String,
}
impl Animal for Labrador {
fn name(&self) -> &str {
&self.name
}
}
impl Dog for Labrador {
fn bark(&self) {
println!("{} 說:汪汪!", self.name());
}
}
fn main() {
let dog = Labrador { name: "小白".to_string() };
dog.bark(); // 可以使用 Dog trait 的方法
println!("狗的名字是:{}", dog.name()); // 也可以使用 Animal trait 的方法
}
use std::ops::{Add, Mul, Display};
use std::fmt;
#[derive(Debug, Clone, Copy)]
struct Vector2D {
x: f64,
y: f64,
}
impl Vector2D {
fn new(x: f64, y: f64) -> Self {
Vector2D { x, y }
}
fn magnitude(&self) -> f64 {
(self.x * self.x + self.y * self.y).sqrt()
}
}
impl Add for Vector2D {
type Output = Vector2D;
fn add(self, other: Vector2D) -> Vector2D {
Vector2D {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
impl Mul<f64> for Vector2D {
type Output = Vector2D;
fn mul(self, scalar: f64) -> Vector2D {
Vector2D {
x: self.x * scalar,
y: self.y * scalar,
}
}
}
impl fmt::Display for Vector2D {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({:.2}, {:.2})", self.x, self.y)
}
}
fn main() {
let v1 = Vector2D::new(1.0, 2.0);
let v2 = Vector2D::new(3.0, 4.0);
let v3 = v1 + v2; // 使用 Add trait
let v4 = v1 * 2.0; // 使用 Mul trait
println!("v1: {}", v1);
println!("v2: {}", v2);
println!("v1 + v2: {}", v3);
println!("v1 * 2: {}", v4);
println!("v3 的長度: {:.2}", v3.magnitude());
}
// ✅ 好:小而專一的 traits
trait Readable {
fn read(&self) -> String;
}
trait Writable {
fn write(&mut self, data: &str) -> Result<(), String>;
}
trait Seekable {
fn seek(&mut self, position: u64) -> Result<(), String>;
}
// 需要多種能力時組合使用
fn process_file<T>(file: &mut T) -> Result<String, String>
where
T: Readable + Writable + Seekable,
{
file.seek(0)?;
let content = file.read();
file.write(&format!("處理過的:{}", content))?;
Ok(content)
}
// ❌ 不好:過大的 trait
trait FileTrait {
fn read(&self) -> String;
fn write(&mut self, data: &str) -> Result<(), String>;
fn seek(&mut self, position: u64) -> Result<(), String>;
fn compress(&self) -> Vec<u8>;
fn encrypt(&self, key: &str) -> Vec<u8>;
// ... 太多職責
}
// ✅ 好的命名
trait Drawable {
fn draw(&self);
}
trait Serializable {
fn serialize(&self) -> String;
}
trait Comparable {
fn compare(&self, other: &Self) -> std::cmp::Ordering;
}
// ❌ 不好的命名
trait Thing { // 太模糊
fn do_stuff(&self);
}
trait T { // 沒有意義
fn method(&self);
}
trait Logger {
fn log(&self, message: &str);
// 提供預設實現
fn info(&self, message: &str) {
self.log(&format!("[INFO] {}", message));
}
fn error(&self, message: &str) {
self.log(&format!("[ERROR] {}", message));
}
fn debug(&self, message: &str) {
self.log(&format!("[DEBUG] {}", message));
}
}
struct ConsoleLogger;
impl Logger for ConsoleLogger {
fn log(&self, message: &str) {
println!("{}", message);
}
// 可以選擇性覆寫預設實現
fn error(&self, message: &str) {
eprintln!("❌ {}", message);
}
}
fn main() {
let logger = ConsoleLogger;
logger.info("應用程式啟動");
logger.error("發生錯誤");
logger.debug("除錯資訊");
}
// Rust Traits 支援預設實現
trait Shape {
fn area(&self) -> f64;
// 預設實現
fn describe(&self) -> String {
format!("這是一個面積為 {:.2} 的形狀", self.area())
}
}
// 可以為現有型別實現 traits(擴展性)
impl Shape for f64 {
fn area(&self) -> f64 {
std::f64::consts::PI * self * self // 假設這是圓的半徑
}
}
fn main() {
let radius = 5.0;
println!("{}", radius.describe()); // f64 現在有了 describe 方法!
}
// Rust 的 traits 是顯式實現的,不像 Go 的隱式實現
trait Writer {
fn write(&mut self, data: &[u8]) -> Result<usize, String>;
}
struct FileWriter {
filename: String,
}
// 必須明確聲明實現 Writer trait
impl Writer for FileWriter {
fn write(&mut self, data: &[u8]) -> Result<usize, String> {
println!("寫入 {} 個位元組到 {}", data.len(), self.filename);
Ok(data.len())
}
}
trait A {
fn method(&self);
}
trait B {
fn method(&self);
}
struct MyStruct;
impl A for MyStruct {
fn method(&self) {
println!("來自 trait A");
}
}
impl B for MyStruct {
fn method(&self) {
println!("來自 trait B");
}
}
fn main() {
let obj = MyStruct;
// obj.method(); // 編譯錯誤:模糊的方法呼叫
// 明確指定要呼叫哪個 trait 的方法
A::method(&obj);
B::method(&obj);
// 或使用 UFCS (Universal Function Call Syntax)
<MyStruct as A>::method(&obj);
<MyStruct as B>::method(&obj);
}
今天我們深入學習了 Rust 的 Traits 系統:
核心概念:
trait
關鍵字定義共享行為impl Trait for Type
語法type
關鍵字定義關聯型別進階特性:
dyn Trait
進行動態分配Add
、Mul
等運算 trait實用技巧:
設計原則:
為什麼 Traits 很重要?
Traits 是 Rust 實現多型和程式碼抽象的核心機制。它們不僅讓我們能夠寫出更加靈活和可重用的程式碼,還保持了 Rust 的零成本抽象原則。掌握 Traits 將大大提升你設計優雅 Rust API 的能力。
為了鞏固今天的學習,嘗試設計一個通用的資料處理管線系統:
功能需求:
技術要求:
技術提示:
trait Processor {
type Input;
type Output;
type Error;
fn process(&mut self, input: Self::Input) -> Result<Self::Output, Self::Error>;
// 預設實現
fn name(&self) -> &'static str {
"未命名處理器"
}
}
trait Pipeline<T> {
type Error;
fn add_processor<P>(self, processor: P) -> Self
where
P: Processor<Input = T, Output = T>;
fn execute(&mut self, input: T) -> Result<T, Self::Error>;
}
// 實現各種處理器:FilterProcessor, MapProcessor, ReduceProcessor 等
這個挑戰將讓你綜合運用 traits 的各種特性:trait 定義、關聯型別、泛型約束、預設實現等。重點是設計一個既靈活又易用的處理管線系統。
明天我們將學習 生命週期 (Lifetimes),這是 Rust 中最具挑戰性但也最重要的概念之一。生命週期與 traits 和泛型結合使用,能讓我們寫出既安全又高效的程式碼!
如果在實作過程中遇到任何問題,歡迎在留言區討論。Traits 是 Rust 程式設計的精髓,多練習不同的設計模式會讓你對 Rust 的抽象能力有更深的理解!
我們明天見!